package network;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

import entities.Player;
import exceptions.MCConnectionException;

import packets.Dispatcher;
import packets.SPacket;
import packets.s2cpackets.S0x00;
import packets.s2cpackets.S0xFF;


/**
 * Connection to a Minecraft server.
 * 
 * Each connection can have a User set, but this is not always true.
 * For example, during handshake the connection has no user.
 * 
 * The class will call the dispatcher on incomming messages,
 * and contains byte-send methods, to send bytes back to the client.
 * 
 */
public class MCConnection implements Runnable {

	private Socket connection;	
	private Dispatcher dispatcher;
	
	private ConnectionWriter w;
	public ConnectionReader r;
	
	/**
	 * When the user is authenticated, the value of the Player variable
	 * is set to the actual entity logged in.
	 */
	private Player player;
	
	/**
	 * Initialize a new connection.
	 * @param connection
	 */
	public MCConnection(Socket connection, Dispatcher dispatcher) {
		this.connection = connection;
		this.dispatcher = dispatcher;
		
		try {
			r = new ConnectionReader(new DataInputStream(connection.getInputStream()));
			w = new ConnectionWriter(new DataOutputStream(connection.getOutputStream()));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	
	/**
	 * Disconnect the current connection.
	 * 
	 * Note that after disconnecting the MCConnection will
	 * also stop its thread.
	 */
	public void disconnect() {
		try {
			this.connection.close();
		} catch (IOException e) {}
	}

	
	public void disconnect(String message) {
		SPacket x = new S0xFF(message);
		try {
			this.send(x);
			Thread.sleep(100);
		}
		catch (MCConnectionException e) {}
		catch (InterruptedException e) {}
		
		this.disconnect();
	}
	
	
	/**
	 * Main client-communication loop.
	 */
	@Override
	public void run() {		
		try {			
			while(true) {
				int msgcommand = r.read();
				if(msgcommand == -1) {break;}
				dispatcher.dispatch(msgcommand, this);
			}
			
		} catch (MCConnectionException e) {
			System.out.println("Connection reset.");
		}
		dispatcher.dispatch(-1, this);
	}


	public synchronized void send(SPacket s) throws MCConnectionException {
		this.w.send(s.getBytes());
	}
	
	
	/**
	 * Sets the player for this connection. This is issued after a succesful login.
	 * @param player
	 */
	public void setPlayer(Player player) {
		//TODO: Assert that the player has not been set already
		this.player = player;
		
		//after a succesful login we can assume the player
		// will be online for a while: 
		// we are going to start pinging the client a lot
		// to ensure a nice connection!
		new Thread(new PingThread(this)).start();
	}
	
	
	/**
	 * Returns the logged in player entity for this connection.
	 * @return The logged in player entity, or null if no player was logged in for this connection (yet).
	 */
	public Player getPlayer() {
		return player;
	}

}



/**
 * Thread wich will ping a client for a undefined amount of time.
 * Thread stops automagically and silently when a connection is
 * closed or an error occurs.
 */
class PingThread implements Runnable {

	private MCConnection conn;
	
	public PingThread(MCConnection conn) {
		this.conn = conn;
	}
	
	@Override
	public void run() {
		SPacket ping = new S0x00(1);
		
		while(true) {
			try {
				Thread.sleep(25000);
				conn.send(ping);
			} catch (InterruptedException e) {
				return;
			} catch (MCConnectionException e) {
				return;
			}
		}
	}
	
}
